#pragma once

#define CPFS_PERM_SUID 04000
#define CPFS_PERM_SGID 02000
#define CPFS_PERM_SVTX 01000

#ifdef __cplusplus
    extern "C" {
#endif

typedef enum {
	NoError,
	BadFileDescriptor,
	TooManyOpenFiles,
	FilesystemFull,
	FilesystemCorrupt,
	FilenameTooLong,
	InvalidPath,
	PathTooLong,
	PathNotFound,
	DirectoryNotEmpty,
	IsRoot,
	FileExists,
	IsDirectory,
	NotDirectory,
	NotSymlink,
    LogicError,
} CpfsErrno;
bool cpfs_errno_isset();
CpfsErrno cpfs_errno_get();
char const *cpfs_errno_str(CpfsErrno);

typedef enum {
	CPFS_ITYP_REG  = 010,
	CPFS_ITYP_DIR  = 004,
	CPFS_ITYP_FIFO = 001,
	CPFS_ITYP_LNK  = 012,
} CpfsInodeType;

typedef int64_t CpfsOff;

typedef int CpfsSizeRet;

typedef uint32_t CpfsUid;

typedef uint32_t CpfsGid;

typedef int64_t CpfsTime;
CpfsTime timespec_to_cpfstime(struct timespec const *ts);
struct timespec cpfstime_to_timespec(CpfsTime ct);

typedef union {
	struct {
		uint16_t suid : 1;
	};
	uint16_t perms;
} CpfsPerms;

typedef struct CpfsParams {
    char *devpath;
    long unsigned namemax;
    uint32_t serial;
} CpfsParams;

struct CpfsStat {
    int64_t ino;
	CpfsInodeType type;
	CpfsPerms perms;
    int32_t nlink;
    uint32_t uid;
    uint32_t gid;
    uint64_t rdev;
	uint64_t size;
    uint32_t blksize;
    CpfsTime atime;
    CpfsTime mtime;
    CpfsTime ctime;
};

struct CpfsStatvfs {
	uint32_t bsize;
	size_t namemax;
	uint64_t blocks;
	uint64_t bfree;
};

typedef struct Cpfs Cpfs;

typedef int CpfsFd;

bool cpfs_mkfs(char const *devpath, CpfsOff size, CpfsOff off, ssize_t blksize);

Cpfs *cpfs_load(char const *devpath, CpfsOff size, CpfsOff off);
bool cpfs_unload(Cpfs *);

struct CpfsParams const *cpfs_params(Cpfs *);
bool cpfs_statvfs(Cpfs *, struct CpfsStatvfs *);

/* these functions operate on paths */
bool cpfs_stat(Cpfs *, char const *path, struct CpfsStat *);
bool cpfs_mknod(Cpfs *, char const *path, CpfsInodeType, CpfsPerms, CpfsUid, CpfsGid);
bool cpfs_utimens(Cpfs *, char const *path, CpfsTime const[2]);
bool cpfs_truncate(Cpfs *, char const *path, CpfsOff len);
bool cpfs_rmdir(Cpfs *, char const *path);
bool cpfs_unlink(Cpfs *, char const *path);
bool cpfs_chmod(Cpfs *, char const *path, CpfsPerms);
bool cpfs_rename(Cpfs *, char const *oldpath, char const *newpath);
bool cpfs_chown(Cpfs *, char const *path, CpfsUid uid, CpfsGid gid);
bool cpfs_symlink(Cpfs *, char const *target, char const *path, CpfsUid, CpfsGid);
bool cpfs_readlink(Cpfs *, char const *path, char *buf, size_t len);
bool cpfs_link(Cpfs *, char const *oldpath, char const *newpath);

/* directory functions */
bool cpfs_readdir(Cpfs *,
    int, // [in] a cpfs instance file descriptor
    CpfsOff *, // [inout] directory offset, in: {0: ".", -2: "..", -1: actual}, out: {0: end of directory}
    char *name, // [out] buffer at least name max + 1 to receive the name
    struct CpfsStat *,
    int fullstat); // [out]
int cpfs_opendir(Cpfs *, char const *path);
bool cpfs_releasedir(Cpfs *, int);

/* file functions */
CpfsFd cpfs_create(Cpfs *, char const *path, CpfsInodeType, CpfsPerms, CpfsUid, CpfsGid);
CpfsFd cpfs_open(Cpfs *, char const *path);
bool cpfs_close(Cpfs *, CpfsFd);
CpfsSizeRet cpfs_read(Cpfs *, CpfsFd, void *buf, size_t count, CpfsOff off);
CpfsSizeRet cpfs_write(Cpfs *, CpfsFd, void const *buf, size_t count, CpfsOff off);
bool cpfs_ftruncate(Cpfs *, CpfsFd fd, CpfsOff len);
bool cpfs_fstat(Cpfs *, CpfsFd fd, struct CpfsStat *);
bool cpfs_fnop(Cpfs *, CpfsFd fd);

#ifdef __cplusplus
    }
#endif
